#include "MD2.h"


     


CMd2::CMd2()
{
	m_pFrames			= NULL;
	m_pTriangles		= NULL;
	m_pTexCoords		= NULL;
	m_pSkins			= NULL;
	m_pCurrentVerts		= NULL;

	m_bIsCustomTex		= false;

	m_CurrentPath[0] = 0;
	m_CurrentTexName[0] = 0;


	m_SMVB				= NULL;

	//m_Timer.Init();
}

CMd2::CMd2(const char *szFile)
{
	m_pFrames			= NULL;
	m_pTriangles		= NULL;
	m_pTexCoords		= NULL;
	m_pSkins			= NULL;
	m_pCurrentVerts		= NULL;

	m_bIsCustomTex		= false;

	m_CurrentPath[0] = 0;
	m_CurrentTexName[0] = 0;

	m_SMVB				= NULL;


	Load(szFile);
	//m_Timer.Init();
}

CMd2::~CMd2()
{
	if(m_pFrames)
	{
		delete [] m_pFrames;
		m_pFrames = NULL;
	}
	if(m_pTexCoords)
	{
		delete [] m_pTexCoords;
		m_pTexCoords = NULL;
	}
	if(m_pTriangles)
	{
		delete [] m_pTriangles;
		m_pTriangles = NULL;
	}
	if(m_pSkins)
	{
		delete [] m_pSkins;
		m_pSkins = NULL;
	}
	if(m_pCurrentVerts)
	{
		delete [] m_pCurrentVerts;
		m_pCurrentVerts = NULL;
	}
	if(	m_SMVB )
	{
		delete [] m_SMVB;
		m_SMVB = NULL;
	}
}


//-------------------------------------------------------------
//- Load
//- Loads an MD2 model from file
//-------------------------------------------------------------
bool CMd2::Load(const char *szFilename)
{
	unsigned char *ucpBuffer = NULL;
	unsigned char *ucpPtr = NULL;
	unsigned char *ucpTmpPtr = NULL; 
	int iFileSize = 0;
	FILE *f = NULL;
	errno_t err;
	
	if( (err = fopen_s(&f, szFilename, "rb")) != 0 )
	{
        AlertWindow::showMessageBox( AlertWindow::WarningIcon,
            "Couldn't find the file!",
            "ModelEditor fail to import file :" + String(szFilename) );

		return false;
	}

	_stprintf_s( m_CurrentPath, L"%s", File(szFilename).getParentDirectory().getFullPathName().toWideCharPointer() );

	//check file size and read it all into the buffer
	int iStart = ftell(f);
	fseek(f, 0, SEEK_END);
	int iEnd = ftell(f);
	fseek(f, 0, SEEK_SET);
	iFileSize = iEnd - iStart;

	//Allocate memory for whole file
	ucpBuffer = new unsigned char[iFileSize];
	ucpPtr = ucpBuffer;

	if( !ucpBuffer )
	{
        AlertWindow::showMessageBox( AlertWindow::WarningIcon,
            "Couldn't Allocate Memory!",
            "ModelEditor fail to allocate memory for file " + String(szFilename) );
		fclose(f);
		return false;
	}

	//Load file into buffer
	if( fread(ucpBuffer, 1, iFileSize, f) != (unsigned)iFileSize )
	{
		AlertWindow::showMessageBox( AlertWindow::WarningIcon,
            "Couldn't load the file!",
            "ModelEditor fail to read file " + String(szFilename) );

		delete [] ucpBuffer;
		ucpBuffer = NULL;
		fclose(f);
		return false;
	}

	//close the file, we don't need it anymore
	fclose(f);

	//get the header
	memcpy(&m_Head, ucpPtr, sizeof(SMD2Header));

	//make sure it is a valid MD2 file before we get going
	if( m_Head.m_iMagicNum != 844121161 || m_Head.m_iVersion != 8 )
	{
		AlertWindow::showMessageBox( AlertWindow::WarningIcon,
            "Version Error!",
            "File " + String(szFilename) + " is not a valid MD2 file" );
	
		delete [] ucpBuffer;
		return false;
	}
	
	ucpTmpPtr = ucpPtr;
	ucpTmpPtr += m_Head.m_iOffsetFrames;

	//read the frames
	m_pFrames = new SMD2Frame[m_Head.m_iNumFrames];
	
	for(int i = 0; i < m_Head.m_iNumFrames; i++)
	{
		float fScale[3];
		float fTrans[3];
		m_pFrames[i].m_pVerts = new SMD2Vert[m_Head.m_iNumVertices];

		//expand the verices
		memcpy(fScale, ucpTmpPtr, 3*sizeof(float));
		ucpTmpPtr += 3*sizeof(float);
		memcpy(fTrans, ucpTmpPtr, 3*sizeof(float));
		ucpTmpPtr += 3*sizeof(float);
		memcpy(m_pFrames[i].m_caName, ucpTmpPtr, 16);
		ucpTmpPtr += 16;
		for(int j = 0; j < m_Head.m_iNumVertices; j++)
		{
			//swap y and z coords to convert to the proper orientation on screen
			m_pFrames[i].m_pVerts[j].m_fVert[0] = ucpTmpPtr[0] * fScale[0] + fTrans[0];
			m_pFrames[i].m_pVerts[j].m_fVert[1] = ucpTmpPtr[2] * fScale[2] + fTrans[2];
			m_pFrames[i].m_pVerts[j].m_fVert[2] = ucpTmpPtr[1] * fScale[1] + fTrans[1];
			m_pFrames[i].m_pVerts[j].m_ucReserved = ucpTmpPtr[3];
			ucpTmpPtr += 4;
		}		
	}

	//Read in the triangles
	ucpTmpPtr = ucpPtr;
	ucpTmpPtr += m_Head.m_iOffsetTriangles;
	m_pTriangles = new SMD2Triangle[m_Head.m_iNumTriangles];
	memcpy(m_pTriangles, ucpTmpPtr, sizeof(unsigned short)*3*2 * m_Head.m_iNumTriangles);

	//Read the U/V texture coords
	ucpTmpPtr = ucpPtr;
	ucpTmpPtr += m_Head.m_iOffsetTexCoords;
	m_pTexCoords = new SMD2TexCoord[m_Head.m_iNumTexCoords];
	
	short *sTexCoords = new short[m_Head.m_iNumTexCoords * 2];
	memcpy(sTexCoords, ucpTmpPtr, sizeof(short)*2 * m_Head.m_iNumTexCoords);

	for(int i = 0; i < m_Head.m_iNumTexCoords; i++)
	{
		m_pTexCoords[i].m_fTex[0] = (float)sTexCoords[2*i] / m_Head.m_iSkinWidthPx;
		m_pTexCoords[i].m_fTex[1] = (float)sTexCoords[2*i+1] / m_Head.m_iSkinHeightPx;
	}
	
	delete [] sTexCoords;
	sTexCoords = NULL;

	//Read the skin filenames
	ucpTmpPtr = ucpPtr;
	ucpTmpPtr += m_Head.m_iOffsetSkins;
	m_pSkins = new SMD2Skin[m_Head.m_iNumSkins];
	

	//Load textures
	for(int i = 0; i < m_Head.m_iNumSkins; i++)
	{
		memcpy(m_pSkins[i].m_caTextureName, ucpTmpPtr, 64);

		ucpTmpPtr += 64;
	}
		
	delete [] ucpBuffer;
	ucpBuffer = NULL;
	return true;
}

//-------------------------------------------------------------
//- CreateStaticMesh
//- Create the model StaticMesh in its initial position (frame 0)
//-------------------------------------------------------------
void CMd2::CreateStaticMesh(UINT skinID)
{
	if(	m_SMVB )
	{
		delete [] m_SMVB;
		m_SMVB = NULL;
	}

	m_SMVB = new OY_UCOLOR_VERTEX_TYPE [m_Head.m_iNumTriangles*3];

	for(int j = 0; j < m_Head.m_iNumTriangles; j++)
	{
		m_SMVB[j*3].x = m_pFrames[0].m_pVerts[m_pTriangles[j].m_sVertIndices[0]].m_fVert[0] * m_ModelScale;
		m_SMVB[j*3].y = m_pFrames[0].m_pVerts[m_pTriangles[j].m_sVertIndices[0]].m_fVert[1] * m_ModelScale;
		m_SMVB[j*3].z = m_pFrames[0].m_pVerts[m_pTriangles[j].m_sVertIndices[0]].m_fVert[2] * m_ModelScale;
		m_SMVB[j*3].VertexColor = 0xFFFFFFFF;
		m_SMVB[j*3].tu = m_pTexCoords[m_pTriangles[j].m_sTexIndices[0]].m_fTex[0];
		m_SMVB[j*3].tv = m_pTexCoords[m_pTriangles[j].m_sTexIndices[0]].m_fTex[1];

		m_SMVB[j*3+1].x = m_pFrames[0].m_pVerts[m_pTriangles[j].m_sVertIndices[1]].m_fVert[0] * m_ModelScale;
		m_SMVB[j*3+1].y = m_pFrames[0].m_pVerts[m_pTriangles[j].m_sVertIndices[1]].m_fVert[1] * m_ModelScale;
		m_SMVB[j*3+1].z = m_pFrames[0].m_pVerts[m_pTriangles[j].m_sVertIndices[1]].m_fVert[2] * m_ModelScale;
		m_SMVB[j*3+1].VertexColor = 0xFFFFFFFF;
		m_SMVB[j*3+1].tu = m_pTexCoords[m_pTriangles[j].m_sTexIndices[1]].m_fTex[0];
		m_SMVB[j*3+1].tv = m_pTexCoords[m_pTriangles[j].m_sTexIndices[1]].m_fTex[1];

		m_SMVB[j*3+2].x = m_pFrames[0].m_pVerts[m_pTriangles[j].m_sVertIndices[2]].m_fVert[0] * m_ModelScale;
		m_SMVB[j*3+2].y = m_pFrames[0].m_pVerts[m_pTriangles[j].m_sVertIndices[2]].m_fVert[1] * m_ModelScale;
		m_SMVB[j*3+2].z = m_pFrames[0].m_pVerts[m_pTriangles[j].m_sVertIndices[2]].m_fVert[2] * m_ModelScale;
		m_SMVB[j*3+2].VertexColor = 0xFFFFFFFF;
		m_SMVB[j*3+2].tu = m_pTexCoords[m_pTriangles[j].m_sTexIndices[2]].m_fTex[0];
		m_SMVB[j*3+2].tv = m_pTexCoords[m_pTriangles[j].m_sTexIndices[2]].m_fTex[1];
	}

	g_pDevice->GetVertexCacheManager()->DeferedCreateStaticBuffer( VID_UCOLOR, skinID, m_Head.m_iNumTriangles*3, 0, m_SMVB, NULL );
	
	return;
}

void CMd2::Render(float ElapsedSeconds)
{
	RenderAnimate( g_DefaultSkin, ElapsedSeconds,
		CModelEditorConfig::GetInstance()->m_fFrames_per_second.get(),
		CModelEditorConfig::GetInstance()->m_UIStartFrameID.get(),
		CModelEditorConfig::GetInstance()->m_UIEndFrameID.get(),
		CModelEditorConfig::GetInstance()->m_bLoopPlayMode );
}

//-------------------------------------------------------------
//- RenderAnimate 
//- Animates the MD2 file	- fspeed is frames per second (MUST < 1000)
//							- uiStartFrame and uiEndFrame is Frame ID
//-------------------------------------------------------------
void CMd2::RenderAnimate(UINT skinID, float ElapsedSeconds, float fSpeed, unsigned int uiStartFrame, unsigned int uiEndFrame, bool bLoop)
{
	static unsigned int uiTotalFrames = 0;			//total number of frames
	static unsigned int uiLastStart = 0, uiLastEnd = 0;	//last start/end parems passed to the function
	static unsigned int uiLastFrame = 0;			//lastframe rendered
	static unsigned int uiMSPerFrame = 0;			//number of milliseconds per frame
	static float fLastInterp = 0;					//Last interpolation value


	// alloc a place to put the interpolated vertices
	if( !m_pCurrentVerts )
		m_pCurrentVerts = new SMD2Vert[m_Head.m_iNumVertices];

	// Prevent invalid frame counts
	if(uiEndFrame > (unsigned int)m_Head.m_iNumFrames-1)
		uiEndFrame = m_Head.m_iNumFrames - 1;
	if(uiStartFrame > (unsigned int)m_Head.m_iNumFrames-1)
		uiStartFrame = m_Head.m_iNumFrames - 1;

	// avoid calculating everything every frame
	if(uiLastStart != uiStartFrame || uiLastEnd != uiEndFrame)
	{
		uiLastStart = uiStartFrame;
		uiLastEnd = uiEndFrame;
		if(uiStartFrame > uiEndFrame)
		{
			uiTotalFrames = m_Head.m_iNumFrames - uiStartFrame + uiEndFrame;
		}
		else
		{
			uiTotalFrames = uiEndFrame - uiStartFrame;
		}
		uiLastFrame = uiStartFrame;
	}
		
	uiMSPerFrame = (unsigned int)(1000 / fSpeed);

	//Calculate the next frame and the interpolation value
	unsigned int uiTime = (unsigned int)(ElapsedSeconds * 1000);
	float fInterpValue = ((float) uiTime / uiMSPerFrame) + fLastInterp;
	fLastInterp = fInterpValue;

	//If the interpolation value is greater than 1, we must increment the frame counter
	while(fInterpValue > 1.0f)
	{
		uiLastFrame++;
		if(uiLastFrame > uiEndFrame)
		{
			if( bLoop )
				uiLastFrame = uiStartFrame;
			else
				uiLastFrame = uiEndFrame;
		}
		fInterpValue -= 1.0f;
		fLastInterp = fInterpValue;
	}

	SMD2Frame* pCurFrame = &m_pFrames[uiLastFrame];
	SMD2Frame* pNextFrame = NULL;

	if( (int)(uiLastFrame + 1) >= m_Head.m_iNumFrames )
	{
		if( bLoop )
			pNextFrame = &m_pFrames[uiStartFrame];
		else
			pNextFrame = pCurFrame;
	}
	else
		pNextFrame = &m_pFrames[uiLastFrame+1];

	// if uiStartFrame == uiEndFrame, It is Single Frame Rendering
	if( uiTotalFrames == 0 )
		fInterpValue = 0;

	//interpolate the vertices
	for(int x = 0; x < m_Head.m_iNumVertices; x++)
	{
		m_pCurrentVerts[x].m_fVert[0] = pCurFrame->m_pVerts[x].m_fVert[0] + (pNextFrame->m_pVerts[x].m_fVert[0] - pCurFrame->m_pVerts[x].m_fVert[0]) * fInterpValue;
		m_pCurrentVerts[x].m_fVert[1] = pCurFrame->m_pVerts[x].m_fVert[1] + (pNextFrame->m_pVerts[x].m_fVert[1] - pCurFrame->m_pVerts[x].m_fVert[1]) * fInterpValue;
		m_pCurrentVerts[x].m_fVert[2] = pCurFrame->m_pVerts[x].m_fVert[2] + (pNextFrame->m_pVerts[x].m_fVert[2] - pCurFrame->m_pVerts[x].m_fVert[2]) * fInterpValue;
	}


	// Fill Vertex Data
	if(	!m_SMVB )
		m_SMVB = new OY_UCOLOR_VERTEX_TYPE [m_Head.m_iNumTriangles*3];
	
	for(int j = 0; j < m_Head.m_iNumTriangles; j++)
	{		
		m_SMVB[j*3].x = m_pCurrentVerts[m_pTriangles[j].m_sVertIndices[0]].m_fVert[0] * m_ModelScale;
		m_SMVB[j*3].y = m_pCurrentVerts[m_pTriangles[j].m_sVertIndices[0]].m_fVert[1] * m_ModelScale;
		m_SMVB[j*3].z = m_pCurrentVerts[m_pTriangles[j].m_sVertIndices[0]].m_fVert[2] * m_ModelScale;
		m_SMVB[j*3].VertexColor = 0xFFFFFFFF;
		m_SMVB[j*3].tu = m_pTexCoords[m_pTriangles[j].m_sTexIndices[0]].m_fTex[0];
		m_SMVB[j*3].tv = m_pTexCoords[m_pTriangles[j].m_sTexIndices[0]].m_fTex[1];

		m_SMVB[j*3+1].x = m_pCurrentVerts[m_pTriangles[j].m_sVertIndices[1]].m_fVert[0] * m_ModelScale;
		m_SMVB[j*3+1].y = m_pCurrentVerts[m_pTriangles[j].m_sVertIndices[1]].m_fVert[1] * m_ModelScale;
		m_SMVB[j*3+1].z = m_pCurrentVerts[m_pTriangles[j].m_sVertIndices[1]].m_fVert[2] * m_ModelScale;
		m_SMVB[j*3+1].VertexColor = 0xFFFFFFFF;
		m_SMVB[j*3+1].tu = m_pTexCoords[m_pTriangles[j].m_sTexIndices[1]].m_fTex[0];
		m_SMVB[j*3+1].tv = m_pTexCoords[m_pTriangles[j].m_sTexIndices[1]].m_fTex[1];

		m_SMVB[j*3+2].x = m_pCurrentVerts[m_pTriangles[j].m_sVertIndices[2]].m_fVert[0] * m_ModelScale;
		m_SMVB[j*3+2].y = m_pCurrentVerts[m_pTriangles[j].m_sVertIndices[2]].m_fVert[1] * m_ModelScale;
		m_SMVB[j*3+2].z = m_pCurrentVerts[m_pTriangles[j].m_sVertIndices[2]].m_fVert[2] * m_ModelScale;
		m_SMVB[j*3+2].VertexColor = 0xFFFFFFFF;
		m_SMVB[j*3+2].tu = m_pTexCoords[m_pTriangles[j].m_sTexIndices[2]].m_fTex[0];
		m_SMVB[j*3+2].tv = m_pTexCoords[m_pTriangles[j].m_sTexIndices[2]].m_fTex[1];
	}

	g_pDevice->GetVertexCacheManager()->RenderDynamicBuffer( VID_UCOLOR, skinID, m_Head.m_iNumTriangles*3, 0, m_SMVB, NULL );
	
	CModelEditorConfig::GetInstance()->m_CurrentFrameID = uiLastFrame;

	return;
}

//-------------------------------------------------------------
//- SetTexture
//- Sets the current skin to one of the skins predefined by the md2 itself
//-------------------------------------------------------------
void CMd2::SetTexture(unsigned int SkinID, int uiTex)
{
	if( uiTex >= m_Head.m_iNumSkins )
		return;
	
	_stprintf_s( m_CurrentTexName, L"%s\\%s", m_CurrentPath, String(m_pSkins[uiTex].m_caTextureName).toWideCharPointer() );
	if( BindCorrectTextureName(m_CurrentTexName) )
	{
		g_pDevice->GetSkinManager()->AddDeferedTexture(SkinID, m_CurrentTexName, 0);
		SetCustomTexture(false);
	}
}

//-------------------------------------------------------------
//- BindCorrectTextureName
//- Texture file name in struct SMD2Skin usually has error file's extension,
//- this function find correct file's extension in local path
//-------------------------------------------------------------
bool CMd2::BindCorrectTextureName(TCHAR (&TexName) [128])
{
	if( File(TexName).exists() )
		return true;
	String FileName = File(TexName).getFileNameWithoutExtension();
	String FilePath(m_CurrentPath);

	if( File(String(m_CurrentPath)+String("\\")+FileName+String(".bmp")).exists() )
	{
		_stprintf_s( TexName, L"%s", String(String(m_CurrentPath)+String("\\")+FileName+String(".bmp")).toWideCharPointer() );
		return true;
	}
	if( File(String(m_CurrentPath)+String("\\")+FileName+String(".jpg")).exists() )
	{
		_stprintf_s( TexName, L"%s", String(String(m_CurrentPath)+String("\\")+FileName+String(".jpg")).toWideCharPointer() );
		return true;
	}
	if( File(String(m_CurrentPath)+String("\\")+FileName+String(".png")).exists() )
	{
		_stprintf_s( TexName, L"%s", String(String(m_CurrentPath)+String("\\")+FileName+String(".png")).toWideCharPointer() );
		return true;
	}
	if( File(String(m_CurrentPath)+String("\\")+FileName+String(".tga")).exists() )
	{
		_stprintf_s( TexName, L"%s", String(String(m_CurrentPath)+String("\\")+FileName+String(".tga")).toWideCharPointer() );
		return true;
	}
	if( File(String(m_CurrentPath)+String("\\")+FileName+String(".dds")).exists() )
	{
		_stprintf_s( TexName, L"%s", String(String(m_CurrentPath)+String("\\")+FileName+String(".dds")).toWideCharPointer() );
		return true;
	}
	return false;
}

// ------------------------------------------------------------
//- Name: SetScaling( float fScale /* = 0.0f  */ )
//- Info: Sets the scaling of the model. 0.0f for no scaling.
//-------------------------------------------------------------
void CMd2::SetScaling( float fScale )
{
	SMD2Vert   *pVertex      = NULL;		// TempVertex
	OyAABB		BoundingBox;				// BBox
	if( fScale == 0.0f )
		return;

	// get the bounding box
	// reset the min and max vertex of the BoundingBox
	BoundingBox.vcMin.x = 999999.0f;
	BoundingBox.vcMin.y = 999999.0f;
	BoundingBox.vcMin.z = 999999.0f;
	BoundingBox.vcMax.x = -999999.0f;
	BoundingBox.vcMax.y = -999999.0f;
	BoundingBox.vcMax.z = -999999.0f;

	// Now loop all known vertices and get BoundingBox
	for( int ulCounter = 0; ulCounter < m_Head.m_iNumVertices; ulCounter++ )
	{
		// get the current Vertex
		pVertex = &m_pFrames[0].m_pVerts[ulCounter];

		// 2. Calc BBox
		BoundingBox.vcMax.x = MAX( BoundingBox.vcMax.x, pVertex->m_fVert[0] );
		BoundingBox.vcMax.y = MAX( BoundingBox.vcMax.y, pVertex->m_fVert[1] );
		BoundingBox.vcMax.z = MAX( BoundingBox.vcMax.z, pVertex->m_fVert[2] );
		BoundingBox.vcMin.x = MIN( BoundingBox.vcMin.x, pVertex->m_fVert[0] );
		BoundingBox.vcMin.y = MIN( BoundingBox.vcMin.y, pVertex->m_fVert[1] );
		BoundingBox.vcMin.z = MIN( BoundingBox.vcMin.z, pVertex->m_fVert[2] );

	}
	// Calc scaling
	m_ModelScale = fScale / ( BoundingBox.vcMax.y - BoundingBox.vcMin.y );

	return;
}

